package ginx

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/gin-gonic/gin"
)

const (
	LIST    = "List"
	CREATE  = "Create"
	UPDATE  = "Update"
	DESTROY = "Destroy"

	empty           = ""
	defaultUrlParam = "id"
	urlPrefix       = "/"
)

var (
	GetAction     = NewAction(MethodGet, empty, false, []string{MethodGet})
	PostAction    = NewAction(MethodPost, empty, false, []string{MethodPost})
	PutAction     = NewAction(MethodPut, empty, false, []string{MethodPut})
	PatchAction   = NewAction(MethodPatch, empty, false, []string{MethodPatch})
	DeleteAction  = NewAction(MethodDelete, empty, false, []string{MethodDelete})
	HeadAction    = NewAction(MethodHead, empty, false, []string{MethodHead})
	ConnectAction = NewAction(MethodConnect, empty, false, []string{MethodConnect})
	OptionsAction = NewAction(MethodOptions, empty, false, []string{MethodOptions})
	TraceAction   = NewAction(MethodTrace, empty, false, []string{MethodTrace})

	MethodsActions = Actions{GetAction, PostAction, PutAction, PatchAction,
		DeleteAction, HeadAction, ConnectAction, OptionsAction, TraceAction}

	ListAction    = NewAction(LIST, empty, false, []string{MethodGet})
	CreateAction  = NewAction(CREATE, empty, false, []string{MethodPost})
	UpdateAction  = NewAction(UPDATE, empty, true, []string{MethodPut})
	DestroyAction = NewAction(DESTROY, empty, true, []string{MethodDelete})

	RestActions = Actions{
		ListAction,
		CreateAction,
		UpdateAction,
		DestroyAction,
	}
)

type Action struct {
	HandlerName,
	UrlPath string
	Detail  bool
	Methods []string
}

// Constructor for action
// @handlerName: handlerName of struct view
// @urlPath: Extract urlPath for current handler
// @detail: If set url param on with this handler
// @methods: All methods that the handler can match
func NewAction(handlerName string, urlPath string, detail bool, methods []string) *Action {
	return &Action{
		HandlerName: handlerName,
		UrlPath:     urlPath,
		Detail:      detail,
		Methods:     methods,
	}
}

// Actions defines a action array.
type Actions []*Action

type ActionGetter interface {
	GetActions() Actions
}

type GinRouter struct {
	Engine            *gin.Engine
	group             *gin.RouterGroup
	relativePath      string
	publicMiddlewares gin.HandlersChain
	trailingSlash     bool
	autoPrefix        bool
	DefaultActions    Actions
}

// Setter for router.autoPrefix
// Call It before Register otherwise it won't be worked
func (self *GinRouter) SetAutoPrefix(autoPrefix bool) {
	self.autoPrefix = autoPrefix
}

// Setter for router.relativePath
// Call It before Register otherwise it won't be worked
func (self *GinRouter) SetRelativePath(relativePath string) {
	self.relativePath = relativePath
}

// Setter for router.trailingSlash
// Call It before Register otherwise it won't be worked
func (self *GinRouter) SetTrailingSlash(trailingSlash bool) {
	self.trailingSlash = trailingSlash
}

func (self *GinRouter) PluginRegister(path string, view interface{}, middleware ...gin.HandlerFunc) {
	if self.group == nil {
		// Call SetRelativePath to change default relativePath
		self.group = self.Engine.Group(self.relativePath)
		// Set public middleware for this router
		self.group.Use(self.publicMiddlewares...)
	}

	// Is set auto prefix for this url path
	if !strings.HasPrefix(urlPrefix, path) {
		if self.autoPrefix {
			path = urlPrefix + path
		}
	}

	value := reflect.ValueOf(view)

	name := strings.ToLower(reflect.TypeOf(view).Elem().Name())

	// If it is a `ActionGetter` type
	// Call GetActions from struct view
	// Otherwise got default actions
	// actions := self.getActions(value)
	vals := value.Type()
	valueNum := value.NumMethod()
	actions := []*Action{}
	for i := 0; i < valueNum; i++ {
		mName := vals.Method(i).Name
		actions = append(actions, &Action{
			HandlerName: mName,
			Methods:     []string{"Post"},
			Detail:      false,
			UrlPath:     empty,
		})
	}
	// Find the legal method based on all actions
	for _, action := range actions {
		if method := value.MethodByName(action.HandlerName); method.IsValid() {
			if handlerFunc, ok := method.Interface().(func(ctx *gin.Context)); ok {

				methodName := strings.ToLower(action.HandlerName)
				relativePath := self.getRelativePathPlugin(value, path, name, methodName, action)
				// Register all handlerFunc to current group
				for _, requestMethod := range action.Methods {
					cMiddleware := self.getHandlerMiddlewares(value, action, middleware)
					self.registerToEngine(requestMethod, relativePath, handlerFunc, cMiddleware)
				}
			}
		}
	}
}

// Register router with this method
// @path: router path
// @view: struct view
// @middleware: middleware for this router
func (self *GinRouter) Register(path string, view interface{}, middleware ...gin.HandlerFunc) {
	if self.group == nil {
		// Call SetRelativePath to change default relativePath
		self.group = self.Engine.Group(self.relativePath)
		// Set public middleware for this router
		self.group.Use(self.publicMiddlewares...)
	}

	// Is set auto prefix for this url path
	if !strings.HasPrefix(urlPrefix, path) {
		if self.autoPrefix {
			path = urlPrefix + path
		}
	}

	value := reflect.ValueOf(view)

	// If it is a `ActionGetter` type
	// Call GetActions from struct view
	// Otherwise got default actions
	// actions := self.getActions(value)

	vals := value.Type()
	valueNum := value.NumMethod()
	actions := []*Action{}
	for i := 0; i < valueNum; i++ {
		mName := vals.Method(i).Name
		actions = append(actions, &Action{
			HandlerName: mName,
			Methods:     []string{"Post"},
			Detail:      false,
			UrlPath:     empty,
		})
	}

	// Find the legal method based on all actions
	for _, action := range actions {

		if method := value.MethodByName(action.HandlerName); method.IsValid() {
			if handlerFunc, ok := method.Interface().(func(ctx *gin.Context)); ok {
				methodName := strings.ToLower(action.HandlerName)
				relativePath := self.getRelativePath(value, path, methodName, action)
				// Register all handlerFunc to current group
				for _, requestMethod := range action.Methods {
					cMiddleware := self.getHandlerMiddlewares(value, action, middleware)
					self.registerToEngine(requestMethod, relativePath, handlerFunc, cMiddleware)
				}
			}
		}
	}
}

// Get actions from struct
func (self *GinRouter) getActions(value reflect.Value) Actions {
	if actionGetter, ok := value.Interface().(ActionGetter); ok {
		actions := actionGetter.GetActions()
		if actions == nil {
			panic(fmt.Errorf("`GetActions` returned cannot be nil "))
		} else {
			return actions
		}
	} else {
		return self.DefaultActions
	}

}

// Get url param from struct
func (*GinRouter) getUrlParam(value reflect.Value) string {
	var urlParam string
	if urlPG, ok := value.Interface().(urlParamGetter); ok {
		if urlParam = urlPG.GetUrlParam(); urlParam == empty {
			panic(fmt.Errorf("`GetUrlParam` returned cannot be empty "))
		}
	} else {
		urlParam = defaultUrlParam
	}
	return urlParam
}

func (self *GinRouter) getRelativePathPlugin(value reflect.Value, path string, name string, methodName string, action *Action) string {
	var relativePath string
	// Set url param if action.Detail is true
	if action.Detail {
		// If it is a `urlParamGetter` type
		// Call GetUrlParam from struct view
		// Otherwise got default urlParam
		urlParam := self.getUrlParam(value)

		// Format url path
		if self.trailingSlash {
			relativePath = fmt.Sprintf("%s/%s/%s/:%s", path, name, methodName, urlParam)
			if action.UrlPath != empty {
				relativePath = fmt.Sprintf("%s/%s/%s", relativePath, name, methodName)
			}
		} else {
			relativePath = fmt.Sprintf("%s/%s/%s/:%s", path, name, methodName, urlParam)
			if action.UrlPath != empty {
				relativePath = fmt.Sprintf("%s/%s/%s", relativePath, name, methodName)
			}
		}

	} else {
		// Format url path
		if self.trailingSlash {
			relativePath = path + urlPrefix
			//if action.UrlPath != empty {
			//	relativePath = fmt.Sprintf("%s/%s/%s/", relativePath, name, action.UrlPath)
			//}
			relativePath = fmt.Sprintf("%s/%s/%s", relativePath, name, methodName)
		} else {
			relativePath = path
			//if action.UrlPath != empty {
			//	relativePath = fmt.Sprintf("%s/%s/%s", relativePath, name, action.UrlPath)
			//}
			relativePath = fmt.Sprintf("%s/%s/%s", relativePath, name, methodName)
		}
	}
	return relativePath
}
func (self *GinRouter) getRelativePath(value reflect.Value, path string, methodName string, action *Action) string {
	var relativePath string

	// Set url param if action.Detail is true
	if action.Detail {
		// If it is a `urlParamGetter` type
		// Call GetUrlParam from struct view
		// Otherwise got default urlParam
		urlParam := self.getUrlParam(value)

		// Format url path
		if self.trailingSlash {
			relativePath = fmt.Sprintf("%s/%s/:%s", path, methodName, urlParam)
			if action.UrlPath != empty {
				relativePath = fmt.Sprintf("%s/%s", relativePath, methodName)
			}
		} else {
			relativePath = fmt.Sprintf("%s/%s/:%s", path, methodName, urlParam)
			if action.UrlPath != empty {
				relativePath = fmt.Sprintf("%s/%s", relativePath, methodName)
			}
		}

	} else {
		// Format url path
		if self.trailingSlash {
			relativePath = path
			//if action.UrlPath != empty {
			//	relativePath = fmt.Sprintf("%s/%s/%s/", relativePath, methodName, action.UrlPath)
			//}
			relativePath = fmt.Sprintf("%s/%s", relativePath, methodName)
		} else {
			relativePath = path
			//if action.UrlPath != empty {
			//	relativePath = fmt.Sprintf("%s/%s/%s", relativePath, methodName, action.UrlPath)
			//}
			relativePath = fmt.Sprintf("%s/%s", relativePath, methodName)
		}
	}
	return relativePath
}

// Get custom middleware from struct
func (*GinRouter) getHandlerMiddlewares2(value reflect.Value, action *Action, middleware gin.HandlersChain) gin.HandlersChain {
	if m, ok := value.Interface().(middlewareGetter); ok {
		return m.GetMiddlewares(action, middleware)
	} else {
		return middleware
	}
}

// Get custom middleware from struct
func (*GinRouter) getHandlerMiddlewares(value reflect.Value, action *Action, middleware gin.HandlersChain) gin.HandlersChain {
	if m, ok := value.Interface().(middlewareGetter); ok {
		return m.GetMiddlewares(action, middleware)
	} else {
		return middleware
	}
}

// Register Handler to engine
func (self *GinRouter) registerToEngine(requestMethod, relativePath string, handlerFunc gin.HandlerFunc, middleware gin.HandlersChain) {
	chain := append(middleware, handlerFunc)
	self.group.Handle(strings.ToUpper(requestMethod), relativePath, chain...)
}

// Default Constructor for router
func Router(engine *gin.Engine, defaultActions Actions, publicMiddlewares ...gin.HandlerFunc) *GinRouter {
	return &GinRouter{
		Engine:            engine,
		publicMiddlewares: publicMiddlewares,
		DefaultActions:    defaultActions,
		autoPrefix:        true,
	}
}

// Method Router
// Set MethodsActions for default actions
func MethodRouter(engine *gin.Engine, publicMiddlewares ...gin.HandlerFunc) *GinRouter {
	return Router(engine, MethodsActions, publicMiddlewares...)
}

// Rest Router
// Set RestActions for default actions
func RestRouter(engine *gin.Engine, publicMiddlewares ...gin.HandlerFunc) *GinRouter {
	r := Router(engine, RestActions, publicMiddlewares...)
	r.SetTrailingSlash(true)
	return r
}

// impl middlewareGetter to generate your url param
type urlParamGetter interface {
	GetUrlParam() string
}

// impl middlewareGetter to generate your own middlewares
type middlewareGetter interface {
	GetMiddlewares(action *Action, middleware gin.HandlersChain) []gin.HandlerFunc
}
